package com.siashan.unipay4j.wx.transfer;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.siashan.unipay4j.core.IPayConfig;
import com.siashan.unipay4j.core.PayChannel;
import com.siashan.unipay4j.core.Result;
import com.siashan.unipay4j.core.exception.Unipay4jOperationNotSupportedException;
import com.siashan.unipay4j.core.transfer.*;
import com.siashan.unipay4j.core.util.EnumUtil;
import com.siashan.unipay4j.core.util.MoneyUtil;
import com.siashan.unipay4j.core.util.StrUtil;
import com.siashan.unipay4j.wx.AbstractWxPay;
import com.siashan.unipay4j.wx.WxPayVersionEnum;
import com.siashan.unipay4j.wx.model.transfer.WxTransferDetail;
import com.siashan.unipay4j.wx.model.transfer.WxTransferModel;
import com.siashan.unipay4j.wx.sdk.WXPayConstants;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.http.HttpStatus.SC_MULTIPLE_CHOICES;
import static org.apache.http.HttpStatus.SC_OK;

/**
 * 微信转账
 *
 * @author siashan
 * @since v1.0.1
 **/
@Slf4j
public class WxPayTransfer extends AbstractWxPay implements ITransfer {


    public WxPayTransfer() {
    }

    public WxPayTransfer(IPayConfig payConfig) {
        super(payConfig);
    }

    /**
     * 单笔转账到账户余额
     * <p>
     * ◆ 当返回错误码为“SYSTEMERROR”时，请不要更换商户订单号，一定要使用原商户订单号重试，否则可能造成重复支付等资金风险。
     * <p>
     * ◆ XML具有可扩展性，因此返回参数可能会有新增，而且顺序可能不完全遵循此文档规范，如果在解析回包的时候发生错误，请商户务必不要换单重试，请商户联系客服确认付款情况。如果有新回包字段，会更新到此API文档中。
     * <p>
     * ◆ 因为错误代码字段err_code的值后续可能会增加，所以商户如果遇到回包返回新的错误码，请商户务必不要换单重试，请商户联系客服确认付款情况。如果有新的错误码，会更新到此API文档中。
     * <p>
     * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考，系统实现时请不要依赖这个字段来做自动化处理。
     * <p>
     * ◆ 请商户在自身的系统中合理设置付款频次并做好并发控制，防范错付风险。
     * <p>
     * ◆ 因商户自身系统设置存在问题导致的资金损失，由商户自行承担。
     *
     * @param model 转账请求实体
     * @return 转账结果
     */
    public Result<TransferResp> execTransferToAccountV2(TransferToAccountModel model) {
        log.info("WxTransfer::=>订单{} 转账请求到达", model.getOutTradeNo());
        try {
            Map<String, String> r = initWxPay().transfer(wrapParam2Account(model));
            log.info("返回报文：" + JSON.toJSONString(r));
            String returnCode = r.get(WXPayConstants.RETURN_CODE);
            if (isReturnSuccess(returnCode)) {
                String resultCode = r.get(WXPayConstants.RESULT_CODE);
                if (isResultSuccess(resultCode)) {
                    return Result.tradeSuccess(wrapperResponse(r));
                } else {
                    String errCode = r.get(WXPayConstants.ERR_CODE);
                    String errCodeDes = r.get(WXPayConstants.ERR_CODE_DES);
                    log.info("WxTransfer::=>订单{} 转账失败,错误代码{},错误原因{}", model.getOutTradeNo(), errCode, errCodeDes);
                    return Result.tradeFail(resultCode, errCode, errCodeDes);
                }
            } else {
                String returnMsg = r.get(WXPayConstants.RETURN_MSG);
                log.info("WxTransfer::=>订单{} 转账失败,错误代码{},错误原因{}", model.getOutTradeNo(), returnCode, returnMsg);
                return Result.returnFail(returnCode, returnMsg);
            }

        } catch (Exception e) {
            log.warn("WxTransfer=>订单{} 转账发生异常", model.getOutTradeNo(), e);
        }
        return Result.exception();
    }


    /**
     * 单笔转账到账户余额
     * <p>
     * 发起商家转账接口。商户可以通过该接口同时向多个用户微信零钱进行转账操作。请求消息中应包含商家批次单号、转账名称、appid、转账总金额、
     * 转账总笔数、转账openid、收款用户姓名等信息。注意受理成功将返回批次单号，此时并不代表转账成功，请通过查单接口查询单据的付款状态
     *
     * @param model 转账请求实体
     * @return 转账结果
     */
    public Result<TransferResp> execTransferToAccountV3(TransferRequestModel model) {
        log.info("WxTransfer::=>商家批次单号{} 转账请求到达", model.getOutBatchNo());
        WxTransferModel wxTransferModel = wapperTransferModel(model);
        WechatPayHttpClientBuilder builder = loadBuilder();
        // ... 接下来，你仍然可以通过builder设置各种参数，来配置你的HttpClient
        // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
        CloseableHttpClient httpClient = builder.build();
        HttpPost httpPost = initHttpPost(WXPayConstants.V3_PAY_URL_TRANSFER_BATCHES, JSON.toJSONString(wxTransferModel));
        try {
            CloseableHttpResponse response = httpClient.execute(httpPost);
            StatusLine statusLine = response.getStatusLine();
            String body = EntityUtils.toString(response.getEntity());
            log.info("WxTransfer::=>商家批次单号{} 转账返回报文:{}", model.getOutBatchNo(), body);
            if (statusLine.getStatusCode() >= SC_OK && statusLine.getStatusCode() < SC_MULTIPLE_CHOICES) {
                log.info("WxTransfer::=>商家批次单号{} 受理  成功", model.getOutBatchNo());
                return Result.tradeSuccess(wrapperResponse(body));
            } else {
                JSONObject bodyJson = JSON.parseObject(body);
                String errCode = bodyJson.getString(WXPayConstants.CODE);
                String errCodeDes = bodyJson.getString(WXPayConstants.MESSAGE);
                log.info("WxTransfer::=>商家批次单号{} 转账失败,错误代码{},错误原因{}", model.getOutBatchNo(), errCode, errCodeDes);
                return Result.tradeFail(errCode, errCodeDes);
            }
        } catch (Exception e) {
            log.warn("WxTransfer=>商家批次单号{} 转账发生异常", model.getOutBatchNo(), e);
        }
        return Result.exception();
    }

    @Override
    public Result<TransferResp> execTransferToAccount(TransferToAccountModel model) {
        WxPayVersionEnum ver = EnumUtil.getIEnum(WxPayVersionEnum.class, wxPayConfig.getVersion());
        if (WxPayVersionEnum.V2.equals(ver)) {
            throw new Unipay4jOperationNotSupportedException("微信支付V3版本不支持该接口");
        }
        return execTransferToAccountV2(model);
    }

    @Override
    public Result<TransferResp> execTransferToAccount(TransferRequestModel model) {
        WxPayVersionEnum ver = EnumUtil.getIEnum(WxPayVersionEnum.class, wxPayConfig.getVersion());
        if (WxPayVersionEnum.V2.equals(ver)) {
            throw new Unipay4jOperationNotSupportedException("微信支付V2版本不支持该接口");
        }
        return execTransferToAccountV3(model);
    }

    /**
     * 单笔转账到银行卡
     * <p>
     * 付款业务是基于微信支付商户平台的资金管理能力，为了协助商户方便地实现向银行卡付款，针对部分有开发能力的商户，提供通过API完成付款到银行卡的功能。
     * <p>
     * 注意事项
     * <p>
     * ◆ XML具有可扩展性，因此返回参数可能会有新增，而且顺序可能不完全遵循此文档规范，如果在解析回包的时候发生错误，请商户务必不要换单重试，请商户联系客服确认付款情况。如果有新回包字段，会更新到此API文档中。
     * <p>
     * ◆ 因为错误代码字段err_code的值后续可能会增加，所以商户如果遇到回包返回新的错误码，请商户务必不要换单重试，请商户联系客服确认付款情况。如果有新的错误码，会更新到此API文档中。
     * <p>
     * ◆ 错误代码描述字段err_code_des和失败原因字段reason只供人工定位问题时做参考，系统实现时不要依赖这个字段来做自动化处理。
     * <p>
     * ◆ 请商户在自身的系统中合理设置付款频次并做好并发控制，防范错付风险。
     * <p>
     * ◆ 因商户自身系统设置存在问题导致的资金损失，由商户自行承担。
     *
     * @param model 付款请求参数
     * @return 付款结果
     */
    @Override
    public Result<TransferResp> execTransferToBank(TransferToBankModel model) {
        log.info("WxTransfer::=>订单{} 转账请求到达", model.getOutTradeNo());
        try {
            Map<String, String> r = initWxPay().transfeToBank(wrapParam2Bank(model));
            String returnCode = r.get(WXPayConstants.RETURN_CODE);
            if (isReturnSuccess(returnCode)) {
                String resultCode = r.get(WXPayConstants.RESULT_CODE);
                if (isResultSuccess(resultCode)) {
                    return Result.tradeSuccess(wrapperResponse(r));
                } else {
                    String errCode = r.get(WXPayConstants.ERR_CODE);
                    String errCodeDes = r.get(WXPayConstants.ERR_CODE_DES);
                    log.info("WxTransfer::=>订单{} 转账失败,错误代码{},错误原因{}", model.getOutTradeNo(), errCode, errCodeDes);
                    return Result.returnFail(errCode, errCodeDes);
                }
            } else {
                String returnMsg = r.get(WXPayConstants.RETURN_MSG);
                log.info("WxTransfer::=>订单{} 转账失败,错误代码{},错误原因{}", model.getOutTradeNo(), returnCode, returnMsg);
                return Result.returnFail(returnCode, returnMsg);
            }

        } catch (Exception e) {
            log.warn("WxTransfer=>订单{} 转账发生异常", model.getOutTradeNo(), e);
        }
        return Result.exception();
    }

    //******************************************   private method ***************************************************//

    private Map<String, String> wrapParam2Account(TransferToAccountModel model) {
        Map<String, String> map = new HashMap<>();
        map.put("partner_trade_no", model.getOutTradeNo());
        map.put("openid", model.getIdentity());
        map.put("check_name", model.isCheckName() ? WxTransferCheckNameEnum.FORCE_CHECK.getValue() : WxTransferCheckNameEnum.NO_CHECK.getValue());
        if (model.isCheckName()) {
            // 如果check_name设置为FORCE_CHECK，则必填用户真实姓名
            map.put("re_user_name", model.getReUserName());
            map.put("check_name", WxTransferCheckNameEnum.FORCE_CHECK.getValue());
        } else {
            map.put("check_name", WxTransferCheckNameEnum.NO_CHECK.getValue());
        }
        map.put("amount", String.valueOf(MoneyUtil.yuan2Cent(model.getAmount())));
        map.put("desc", model.getRemark());
        log.info("转账报文:" + JSON.toJSONString(map));
        return map;
    }

    /**
     * 封装转账到银行卡请求参数
     *
     * @param model 请求参数
     * @return 请求参数
     */
    private Map<String, String> wrapParam2Bank(TransferToBankModel model) {
        Map<String, String> map = new HashMap<>();
        map.put("partner_trade_no", model.getOutTradeNo());
        map.put("enc_bank_no", model.getBankNo());
        map.put("enc_true_name", model.getAccountName());
        map.put("bank_code", model.getBankCode());
        map.put("amount", String.valueOf(MoneyUtil.yuan2Cent(model.getAmount())));
        map.put("desc", model.getRemark());
        return map;
    }

    /**
     * 转账成功时返回的参数
     *
     * @param r 微信返回报文
     * @return TransferResponse
     */
    private TransferResp wrapperResponse(Map<String, String> r) {
        TransferResp response = new TransferResp();
        response.setPayChannel(PayChannel.WX);
        response.setTransferState(WxTransferState.SUCCESS.getTransferState());
        // 商户订单号
        response.setOutTradeNo(r.get("partner_trade_no"));
        // 微信付款单号
        response.setTradeNo(r.get("payment_no"));
        response.setBody(JSON.toJSONString(r));
        return response;
    }


    /**
     * 转账成功时返回的参数
     *
     * @param body 微信返回报文
     * @return TransferResponse
     */
    private TransferResp wrapperResponse(String body) {
        JSONObject jb = JSON.parseObject(body);
        TransferResp response = new TransferResp();
        response.setPayChannel(PayChannel.WX);
        response.setTransferState(WxTransferState.SUCCESS.getTransferState());
        // 商户订单号
        response.setOutTradeNo(jb.getString("out_batch_no"));
        // 微信付款单号
        response.setTradeNo(jb.getString("batch_id"));
        response.setBody(body);
        return response;
    }


    private WxTransferModel wapperTransferModel(TransferRequestModel model) {
        WxTransferModel wxTransferModel = new WxTransferModel();
        wxTransferModel.setAppid(wxPayConfig.getAppId());
        wxTransferModel.setOutBatchNo(model.getOutBatchNo());
        wxTransferModel.setBatchName(model.getBatchName());
        wxTransferModel.setBatchRemark(model.getBatchRemark());
        wxTransferModel.setTotalAmount(MoneyUtil.yuan2Cent(model.getTotalAmount()));
        wxTransferModel.setTotalNum(model.getTotalNum());
        List<TransferDetail> detailList = model.getTransferDetailList();
        if (null != detailList && !detailList.isEmpty()) {
            List<WxTransferDetail> wxDetails = new ArrayList<>();
            WxTransferDetail detail = null;
            for (TransferDetail d : detailList) {
                detail = new WxTransferDetail();
                detail.setOutDetailNo(d.getOutDetailNo());
                detail.setTransferAmount(MoneyUtil.yuan2Cent(d.getTransferAmount()));
                detail.setTransferRemark(d.getTransferRemark());
                detail.setOpenid(d.getOpenid());
                if (StrUtil.isNotEmpty(d.getUserName())) {
                    detail.setUserName(d.getUserName());
                }
                wxDetails.add(detail);
            }
            wxTransferModel.setTransferDetailList(wxDetails);
        }
        return wxTransferModel;
    }
}
